package multiThread.security;

import multiThread.pojo.Account;

/**
 * 模拟银行账户取钱操作
 * @projectName: java-sample
 * @description:
 * @author: Adam
 * @create: 2019-12-30 18:58
 **/

/**
 * 解决线程安全问题 需要使用同步监视器 使用同步监视器的通用方法就是同步代码块
 * synchronized(obj){
 * }
 * 除此之外 还有同步方法 即使用synchronized关键字修饰某个方法
 * 对于同步方法，无须显示指定同步监视器，同步方法的同步监视器是this，也就是该对象本身
 * synchroized 关键字可以修饰方法，可以修饰代码块，但不能修饰构造器、属性等。
 * 任何时刻只能有一个线程可以获得对同步监视器的锁定，当同步代码块执行完成后，该线程会释放对该同步监视器的锁定
 * 同步是指：当程序1调用程序2时，程序1停下不动，直到程序2完成回到程序1来，程序1才继续执行下去。
 * 异步是指：当程序1调用程序2时，程序1径自继续自己的下一个动作，不受程序2的的影响。
 * 虽然java程序允许使用任何对象作为同步监视器，但通常推荐使用可能被并发访问的共享资源充当同步监视器
 */

public class DrawThread extends Thread {

    //模拟用户账户
    private Account account;

    //当前取钱线程所希望去的钱数
    private int drawAccount;


    public DrawThread(String name,Account account,int drawAccount){
        super(name);
        this.account = account;
        this.drawAccount = drawAccount;
    }

    //当多个线程修改同一个共享数据时，将涉及数据安全问题
    //run方法的方法体不具有同步安全性
    @Override
    public void run(){

        //使用account作为同步监视器，任何线程进入下面代码块之前，必须先获得account账户的锁定，其他线程无法获得锁，也就无法修改它
        //这种做法符合 加锁-> 修改 -> 释放锁 的逻辑
        synchronized (account){
            //账户余额大于等于取钱数目
            if (account.getBalance() >= drawAccount){
                //吐出钞票
                System.out.println(getName()+"取钱成功!吐出钞票!"+drawAccount);
                try {
                    //强制线程切换  该线程还未扣减  线程暂时执行  另一个线程进来扣减  该线程不做校验直接进来扣减
                    //如果去掉线程睡眠，则线程安全问题随机出现，如果不去掉则必现
                    Thread.sleep(1);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                //修改余额
                account.setBalance(account.getBalance()-drawAccount);
                System.out.println("\t余额为:"+account.getBalance());
            }else {//账户余额小于取钱数目
                System.out.println("取钱失败！余额不足!");
            }
        }
    }
}
